using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using System.Runtime.Serialization.Formatters.Binary;
using System.Linq;
using System.Text.RegularExpressions;
#if xmpp
using agsXMPP;
using agsXMPP.protocol.client;
using agsXMPP.Xml;
using agsXMPP.Xml.Dom;
#endif

namespace CincinnatiLibraryCheckAvailability
{
    public partial class Form1 : Form
    {
        private System.Threading.Timer timerHolds;
        private const string USERID = "49954977";
        private const string USERPIN = "8176";
        private string wb3state = "";
        private Queue<LibraryItem> itemsToGet = new Queue<LibraryItem>();
        private HotKey NewHotKey;
        private bool KeyListening = false;
        private bool working = false;
        private bool firstPass = true;
        private int counter = 0;
#if xmpp
        private bool affinitySet = false;
        private List<Jid> lRespond = new List<Jid>();
        private XmppClientConnection xmppClient;
#endif
        //List<string> libraries = new List<string>();
        TreeNode ActiveNode = null;
        string QueryString = "";
        string MasterEmail = "";
        private bool setVisible = true;

        public Form1(string[] args)
        {
            InitializeComponent();
#if DEBUG
            TopMost = false;
#endif
            foreach (string s in args)
            {
                if (s.Trim().ToLower().Equals("/startup"))
                {
                    setVisible = false;
                    Hide();
                }
            }
            LoadEmail();
#if xmpp
            InitializeXmppClient();
#endif
            DateTime tenThirty = DateTime.Today.AddHours(22.5);
            if (DateTime.Now >= tenThirty)
                tenThirty = tenThirty.AddHours(12);
            TimeSpan timeout = tenThirty - DateTime.Now;

#if DEBUG
            timeout = TimeSpan.FromSeconds(15);
#endif

            timerHolds = new System.Threading.Timer(new System.Threading.TimerCallback(TrySomeHolds), null, timeout, TimeSpan.FromDays(1));
            Libraries.Load();
            Items.Load();
            LoadState();
            if (Libraries.All == null || Libraries.All.Count == 0)
                webBrowser2.Navigate("http://www.cincinnatilibrary.org/info/hours.asp");
            else
            {
                foreach (LibraryItem i in Items.All)
                {
                    foreach (Library lib in i.StatusAt.Keys)
                    {
                        UpdateTree(null, i, Library.FromName(lib.Name));
                    }
                }
                UpdateAllRanks();
                listIgnoredLibraries.Items.AddRange(Libraries.Ignored.ToArray());
                cListMonitoredLibraries.Items.AddRange(Libraries.Monitored.ToArray());
                foreach (Library l in Libraries.ThatGenerateEmail)
                {
                    int i = cListMonitoredLibraries.Items.IndexOf(l);
                    if (i >= 0)
                        cListMonitoredLibraries.SetItemChecked(i, true);
                }
            }
            Settings.ListBoxHotKeys = listHotKeys;
            if (File.Exists("settings.conf"))
            {
                Settings.LoadFromFile("settings.conf", Handle);
                numHolds.Value = Settings.NumHolds;
            }
            else if (File.Exists("hotkeys"))
                Settings.LoadFromFile("hotkeys", Handle);
            else
            {
                Settings.AddHotKey(new HotKey(Keys.C, KeyMod.Win, Handle));
                Settings.AddHotKey(new HotKey(Keys.MButton | Keys.Back | Keys.Space | Keys.F17, KeyMod.None, Handle));
                Settings.AddHotKey(new HotKey(Keys.L, KeyMod.Alt, Handle));
            }
        }

        #region XMPP Functions
#if xmpp
        private void InitializeXmppClient()
        {
            xmppClient = new XmppClientConnection("gmail.com");
            xmppClient.SocketConnectionType = agsXMPP.net.SocketConnectionType.Direct;
            xmppClient.ConnectServer = "talk.google.com";
            xmppClient.Username = "librarybot@gmail.com";
            xmppClient.Password = "cincinnatus";
            xmppClient.Resource = MasterEmail.Substring(0, MasterEmail.IndexOf('@'));
            xmppClient.KeepAlive = true;
            xmppClient.OnLogin += new ObjectHandler(xmppClient_OnLogin);
            xmppClient.OnMessage += new XmppClientConnection.MessageHandler(xmppClient_OnMessage);
            xmppClient.OnPresence += new XmppClientConnection.PresenceHandler(xmppClient_OnPresence);
            xmppClient.OnIq += new StreamHandler(xmppClient_OnIq);
            xmppClient.OnXmppConnectionStateChanged += new XmppConnection.XmppConnectionStateHandler(xmppClient_OnXmppConnectionStateChanged);
            xmppClient.OnSocketError += new ErrorHandler(xmppClient_OnSocketError);
            xmppClient.OnClose += new ObjectHandler(xmppClient_OnClose);
        }

        void xmppClient_OnClose(object sender)
        {
            if (InvokeRequired)
                BeginInvoke(new ObjectHandler(xmppClient_OnClose), sender);
            else
            {
                //Log("On_Close() called!");
                timer2.Enabled = true;
            }
        }

        void xmppClient_OnSocketError(object sender, Exception ex)
        {
            if (InvokeRequired)
            {
                BeginInvoke(new ErrorHandler(xmppClient_OnSocketError), sender, ex);
                return;
            }
            if (ex.GetType() == typeof(ObjectDisposedException))
            {
                Log("Object Disposed Exception: " + ex.ToString());
                InitializeXmppClient();
                xmppClient.Open();
            }
            else
                try
                {
                    xmppClient.SendMyPresence();
                }
                catch (Exception e)
                {
                    Log("Exception thrown while dealing with socket exception: " + ex.ToString() + "\r\n" + e.ToString() + "\r\n");
                }
        }

        void xmppClient_OnXmppConnectionStateChanged(object sender, XmppConnectionState state)
        {
            return;
            if (InvokeRequired)
            {
                BeginInvoke(new XmppConnection.XmppConnectionStateHandler(xmppClient_OnXmppConnectionStateChanged), sender, state);
                return;
            }
        }

        private void xmppClient_OnIq(object sender, agsXMPP.Xml.Dom.Node e)
        {
            if (InvokeRequired)
            {
                // Windows Forms are not Thread Safe, we need to invoke this :(
                // We're not in the UI thread, so we need to call BeginInvoke				
                BeginInvoke(new StreamHandler(xmppClient_OnIq), new object[] { sender, e });
                return;
            }

            agsXMPP.protocol.client.IQ iq = e as IQ;

            if (iq != null)
            {
                // No Iq with query
                if (iq.HasTag(typeof(agsXMPP.protocol.extensions.si.SI)))
                {
                    if (iq.Type == IqType.set)
                    {
                        agsXMPP.protocol.extensions.si.SI si = iq.SelectSingleElement(typeof(agsXMPP.protocol.extensions.si.SI)) as agsXMPP.protocol.extensions.si.SI;

                        agsXMPP.protocol.extensions.filetransfer.File file = si.File;
                        if (file != null)
                        {
                            // somebody wants to send a file to us
                            Console.WriteLine(file.Size.ToString());
                            Console.WriteLine(file.Name);
                            //frmFileTransfer frmFile = new frmFileTransfer(XmppCon, iq);
                            //frmFile.Show();
                        }
                    }
                }
                else
                {
                    Element query = iq.Query;

                    if (query != null)
                    {
                        if (query.GetType() == typeof(agsXMPP.protocol.iq.version.Version))
                        {
                            // its a version IQ VersionIQ
                            agsXMPP.protocol.iq.version.Version version = query as agsXMPP.protocol.iq.version.Version;
                            if (iq.Type == IqType.get)
                            {
                                // Somebody wants to know our client version, so send it back
                                iq.SwitchDirection();
                                iq.Type = IqType.result;

                                version.Name = "MiniClient";
                                version.Ver = "0.5";
                                version.Os = Environment.OSVersion.ToString();

                                xmppClient.Send(iq);
                            }
                        }
                    }
                }
            }
        }

        void xmppClient_OnPresence(object sender, Presence pres)
        {
            if (InvokeRequired)
			{	
				// Windows Forms are not Thread Safe, we need to invoke this :(
				// We're not in the UI thread, so we need to call BeginInvoke				
				BeginInvoke(new XmppClientConnection.PresenceHandler(xmppClient_OnPresence), sender, pres);
				return;
			}
            if (pres.Type == PresenceType.subscribe)
            {
                PresenceManager pm = new PresenceManager(xmppClient);
                if (MasterEmail.StartsWith(pres.From.User))
                    pm.ApproveSubscriptionRequest(pres.From);
                else
                {
                    if (DialogResult.Yes == MessageBox.Show("Accept connection request from " + pres.From.ToString() + "?", "New user requesting access to chatbot", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1))
                        pm.ApproveSubscriptionRequest(pres.From);
                    else pm.RefuseSubscriptionRequest(pres.From);
                }
            }
        }

        void xmppClient_OnMessage(object sender, agsXMPP.protocol.client.Message msg)
        {
            if (InvokeRequired)
            {
                // Windows Forms are not Thread Safe, we need to invoke this :(
                // We're not in the UI thread, so we need to call BeginInvoke				
                BeginInvoke(new XmppClientConnection.MessageHandler(xmppClient_OnMessage), sender, msg);
                return;
            }
            List<Library> libs;
            if (!affinitySet && MasterEmail.StartsWith(msg.From.User))
            {
                if (!lRespond.Contains(msg.From))
                    lRespond.Add(msg.From);
            }
            if (msg.Body == "whois")
            {
                xmppClient.Send(new agsXMPP.protocol.client.Message(msg.From, "monitoring for " + MasterEmail));
            }
            else if (msg.Body.StartsWith("set affinity "))
            {
                if (MasterEmail.StartsWith(msg.From.User))
                    affinitySet = true;
                string user = msg.Body.Substring(13);
                if (MasterEmail.StartsWith(user))
                {
                    if (!lRespond.Contains(msg.From))
                        lRespond.Add(msg.From);
                    xmppClient.Send(new agsXMPP.protocol.client.Message(msg.From, "monitoring for " + MasterEmail));
                }
                else
                {
                    if (lRespond.Contains(msg.From))
                        lRespond.Remove(msg.From);
                }
                return;
            }
            if(!lRespond.Contains(msg.From))// && !MasterEmail.StartsWith(msg.From.User) && msg.From.User.ToLower() != "abloomfield")
                return;
            if (msg.Body == "help")
            {
                xmppClient.Send(new agsXMPP.protocol.client.Message(msg.From,
                    @"Available commands:
whois
list libraries
list items
<library branch> (shows what items are available at that branch
<url> (adds the url to the list)"));
            }
            else if (msg.Body == "list libraries")
            {
                string libraryList = "";
                foreach (Library l in Libraries.All)
                {
                    libraryList += l.Name + (l.IsMonitored ? (l.generatesEmail ? " (generates e-mail)" : "") : " (ignored)") + "\r\n";
                }
                xmppClient.Send(new agsXMPP.protocol.client.Message(msg.From, libraryList));
            }
            else if (msg.Body == "list items")
            {
                string itemList = "";
                foreach (LibraryItem i in Items.All)
                {
                    itemList += i.ToString() + "\r\n";
                }
                xmppClient.Send(new agsXMPP.protocol.client.Message(msg.From, itemList));
            }
            else if (msg.Body.StartsWith("http://catalog.cincinnatilibrary.org/uhtbin/ckey/"))
            {
                string s = msg.Body.Substring(msg.Body.IndexOf("http://"));
                if (Items.ContainsURL(s))
                {
                    xmppClient.Send(new agsXMPP.protocol.client.Message(msg.From, Items.FromUrl(s) + " is already in my list"));
                    return;
                }
                Items.Add(s);
                Items.Save();
                if (!firstPass && !working)
                {
                    timer1.Enabled = false;
                    ScanItem(Items.Next);
                }
            }
            else if ((libs = Libraries.FindAll(msg.Body)) != null && libs.Count > 0)
            {
                foreach (Library lib in libs)
                {
                    string response = lib.Name + "\r\n";
                    foreach (LibraryItem item in Items.At(lib))
                    {
                        if (item.StatusAt[lib].IsAvaliable)
                        {
                            response += item.URL + "\r\n" + item.Title + "\r\n(";
                            foreach (ItemStatus s in item.StatusAt[lib].Statuses)
                            {
                                if (s.IsAvaliable)
                                    response += response.EndsWith("(") ? s.ToString() : (", " + s);
                            }
                            response += ")\r\n\r\n";
                        }
                    }
                    if (response.Length > lib.Name.Length + 2)
                        xmppClient.Send(new agsXMPP.protocol.client.Message(msg.From, response));
                    else xmppClient.Send(new agsXMPP.protocol.client.Message(msg.From, "No items available at " + lib.Name));
                }
            }
        }

        void xmppClient_OnLogin(object sender)
        {
            if (InvokeRequired)
            {
                // Windows Forms are not Thread Safe, we need to invoke this :(
                // We're not in the UI thread, so we need to call BeginInvoke				
                BeginInvoke(new ObjectHandler(xmppClient_OnLogin), sender);
                return;
            }
            timer2.Enabled = false;
            //Log("xmppClient_OnLogin()");
            xmppClient.Status = "Awaiting commands";
            xmppClient.Show = ShowType.chat;
            xmppClient.SendMyPresence();
            //xmppClient.Send(new agsXMPP.protocol.client.Message("bhb123@gmail.com", MessageType.chat, "testing jabber"));
        }
#endif
        #endregion

        public int ItemCount
        {
            set
            {
                lblItemCount.Text = value + " Items";
            }
        }

        private void TrySomeHolds(object o)
        {
            if (InvokeRequired)
            {
                Invoke(new System.Threading.TimerCallback(TrySomeHolds), o);
            }
            else
            {   
                wb3state = "";
                try
                {
                    webBrowser3.Navigate("http://catalog.cincinnatilibrary.org/uhtbin/cgisirsi/x/0/0/49");
                }
                catch (Exception e)
                {
                    Log(e.ToString());
                }
            }
        }

        private void LoadState()
        {
            //Open the file written above and read values from it.
            if (!File.Exists("State.data"))
                return;
            Stream stream = File.Open("State.data", FileMode.Open);
            BinaryFormatter bformatter = new BinaryFormatter();
            State state;
            try
            {
                state = (State)bformatter.Deserialize(stream);
                Size = state.Size;
            }
            catch (Exception)
            {
            }
            finally
            {
                stream.Close();
            }
        }


        private void SaveState()
        {
            State state = new State();
            state.Size = Size;
            Stream stream = File.Open("State.data", FileMode.Create);
            BinaryFormatter bformatter = new BinaryFormatter();

            bformatter.Serialize(stream, state);
            stream.Close();
        }

        private void ScanItem(LibraryItem item)
        {
            if (item == null)
            {
                firstPass = false;
                return;
            }
            working = true;
            foreach (TreeNode n in tvItems.Nodes.Find(item.URL, true))
                n.BackColor = Color.Yellow;
            foreach (TreeNode tn in tvLibraries.Nodes.Find(item.URL, true))
            {
                tn.ToolTipText += "\r\nupdating...";
                tn.BackColor = Color.Yellow;
            }
            lblStatus.Text = "Navigating to " + item;
            webBrowser1.Navigate(item.URL);
            timer3.Enabled = true;
            timer3.Start();
        }

        private void LoadEmail()
        {
            try
            {
                StreamReader sr = new StreamReader("email.txt");
                MasterEmail = sr.ReadToEnd().Trim();
                txtEmail.Text = MasterEmail;
                sr.Close();
#if xmpp
                if (xmppClient != null)
                {
                    xmppClient.Resource = MasterEmail.Substring(0, MasterEmail.IndexOf('@'));
                    if(xmppClient.XmppConnectionState == XmppConnectionState.Connected)
                        xmppClient.Close();
                }
#endif
            }
            catch (Exception e)
            { Log(e.ToString()); }
        }

        private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            Visible = setVisible;
            timer3.Stop();
            timer3.Enabled = false;
            try
            {
                ScanWebPage(((WebBrowser)sender).Document);
                counter++;
                counter %= 10;
                if(counter==0)
                    Items.Save();
            }
            catch (Exception ex)
            {
                Log("Error on item \"" + Items.Current + "\": " + ex.ToString());
            }
            // Every Half hour go through all the items
            timer1.Interval = 1800000 / (Items.Count + 1);
            if (timer1.Interval >= 5000)
                timer1.Interval -= 4500;
            else timer1.Interval = 500;
            if (Items.OnLastItem)
            {
                timer1.Enabled = true;
                firstPass = false;
                working = false;
                return;
            }
            if (firstPass || Items.Peek.StatusAt.Count==0)
            {
                ScanItem(Items.Next);
            }
            else
            {
                timer1.Enabled = true;
                working = false;
            }
        }

        private void ScanWebPage(HtmlDocument document)
        {
            try
            {
                ScanWebPage(document, document.Url.ToString());
            }
            catch (Exception e)
            {
                Log(e.ToString());
            }
        }

        private void ScanWebPage(HtmlDocument document, string url)
        {
            if (!url.ToLower().StartsWith("http:"))
            {
                UpdateTree(null, Items.Current, null);
                return;
            }
            bool addToList = false;
            LibraryItem item = Items.FromUrl(url);
            if (item == null)
            {
                item = new LibraryItem(url);
                addToList = true;
            }
            try
            {
                foreach (HtmlElement span in document.GetElementsByTagName("span"))
                {
                    if (span.OuterHtml.ToLower().Contains("class=title"))
                    {
                        item.Title = span.InnerText;
                        break;
                    }
                }
                Library library = null;
                int column = 3;
                ItemStatus oldStatus = null;
                ItemStatus curStatus = null;
                List<Library> libraries = new List<Library>();
                foreach (HtmlElement tr in document.GetElementsByTagName("tr"))
                {
                    HtmlElementCollection cell = tr.GetElementsByTagName("td");
                    if (cell.Count < 3)
                    {
                        if (curStatus != null)
                        {
                            item.StatusAt[library] = curStatus.Finalize();
                            curStatus = null;
                            UpdateTree(oldStatus, item, library);
                        }
                        continue;
                    }
                    // if this row is the first for a library
                    if (cell[0].InnerText.Trim().Length > 0 && cell[1].InnerText.Trim().ToLower()=="copies")
                    {
                        if (curStatus != null) // if we have a current status, better update that one (it must be for the most recently parsed library)
                        {
                            item.StatusAt[library] = curStatus.Finalize();
                            curStatus = null; 
                            UpdateTree(oldStatus, item, library);
                            
                        }
                        oldStatus = null;
                        library = Library.FromName(cell[0].InnerText.Trim());
                        if (library == null)
                            continue;
                        libraries.Add(library);
                        if (item.StatusAt.ContainsKey(library))
                        {
                            oldStatus = item.StatusAt[library];
                            item.StatusAt.Remove(library);
                        }
                    }
                    if (curStatus != null)
                    {
                        int quantity = 1;
                        try
                        {
                            quantity = Int32.Parse(cell[column - 2].InnerText);
                        }
                        catch (Exception ex)
                        {
                            Log("Exception when tried to parse column " + (column - 2) + " of {" + tr.OuterHtml + "} as an int:" + ex.ToString());
                        }
                        string material = cell[column - 1].InnerText.Trim().ToLower();
                        bool isNewRelease = material.Contains("new release");
                        if (!isNewRelease && item.IsNewRelease)
                        {
                            // No longer new release???
                            // Set its rank to -1...Top of the list
                            item.IsNewRelease = false;
                            item.Rank = -1;
                        }
                        else
                        {
                            item.IsNewRelease = isNewRelease;
                        }
                        string location = cell[column].InnerText.Trim();
                        curStatus.Add(quantity, location);
                        continue;
                    }
                    if (library == null && (item.Title==null || item.Title.Length==0) )
                    {
                        // This item is invalid.  Remove it from the list.
                        Items.All.Remove(item);
                        return;
                    }
                    curStatus = new ItemStatus(oldStatus);
                }
                if (libraries.Count > 0)
                {
                    Library[] temp = new Library[item.StatusAt.Count];
                    item.StatusAt.Keys.CopyTo(temp, 0);
                    bool loggedHtml = false;
                    foreach (Library l in temp)
                    {
                        if (!libraries.Contains(l))
                        {
                            if (!loggedHtml)
                            {
                                Log("\r\nHTML: " + document.Body.OuterHtml + "\r\n\r\n");
                                loggedHtml = true;
                            }
                            Log(item + " is no longer at " + l);
                            // the item is no longer at this library.  Remove it from the status list, and remove it from the tree.
                            oldStatus = item.StatusAt[l];
                            item.StatusAt[l] = null;
                            UpdateTree(oldStatus, item, l);
                            item.StatusAt.Remove(l);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Log("Exception parsing URL (" + url + "):" + ex.ToString());
            }
            if (item.Title == null || item.Title.Length == 0)
            {
                // This item is invalid.  Remove it from the list.
                Items.All.Remove(item);
                return;
            }
            UpdateAllRanks();
            if (addToList)
            {
                Items.Add(item);
                Items.Save();
                UpdateItemCount();
            }
            lblStatus.Text = "Updated " + item;
            //OPE
        }

        private void UpdateTree(ItemStatus oldStatus, LibraryItem item, Library library)
        {
            try
            {
                // Add Item to tvItems regardless of whether this library is monitored.
                TreeNode[] nodes = tvItems.Nodes.Find(item.URL, true);
                TreeNode node;
                string key = item.Status == Status.Default
                    ? item.InList
                    : item.Status.AsString();
                node = tvItems.Nodes[key];
                int i;
                if (node == null)
                {
                    // Sort root nodes in alphabetical order
                    for (i = 0; i < tvItems.Nodes.Count; i++)
                    {
                        if (key.CompareTo(tvItems.Nodes[i].Text) < 0)
                            break;
                    }
                    node = tvItems.Nodes.Insert(i, key, key);
                } // node is the parent node (status or name of list)

                if (item.Status != Status.Default)
                {
                    // add an extra node for the name of its list.
                    key = item.InList;
                    TreeNode temp = node;
                    node = temp.Nodes[key];
                    if (node == null)
                    {
                        // Sort nodes in alphabetical order
                        for (i = 0; i < temp.Nodes.Count; i++)
                        {
                            if (key.CompareTo(temp.Nodes[i].Text) < 0)
                                break;
                        }
                        node = temp.Nodes.Insert(i, key, key);
                    } // node is the parent node (name of list)
                }

                if (nodes.Length > 0 && nodes[0].Parent == node)
                {
                    node = nodes[0];
                    node.BackColor = Color.Empty;
                    if (node.Text != item.ToString())
                        node.Text = item.ToString();
                }
                else
                {
                    if (nodes.Length > 0)
                        nodes[0].Remove();
                    if (item.Status != Status.Default)
                    {
                        // Sort alphabetically
                        for (i = 0; i < node.Nodes.Count; i++)
                        {
                            if (item.Title.CompareTo(node.Nodes[i].Text) < 0)
                                break;
                        }
                    }
                    else
                    {
                        // Sort by rank
                        for (i = node.Nodes.Count - 1; i >= 0; i--)
                        {
                            int diff = item.Rank - Items.FromUrl(node.Nodes[i].Name).Rank;
                            if (diff > 0 || (diff == 0 && item.Title.CompareTo(node.Nodes[i].Text) > 0))
                            {
                                i += 1;
                                break;
                            }
                        }
                    }
                    node = node.Nodes.Insert(i, item.URL, item.ToString());
                    node.Checked = item.PutOnHold;
                }
                node.ContextMenuStrip = contextMenuStripItem;
                if (library == null)
                    return;
                ItemStatus curStatus = item.StatusAt[library];
                if (curStatus!=null && !curStatus.Equals(oldStatus))
                    curStatus.MostRecentChange = DateTime.Now;
                if(curStatus!=null)
                    node.ToolTipText = "Last Updated: " + curStatus.LastUpdated.ToString("HH:mm:ss");
                if (!library.IsMonitored)
                    return;
                bool newlyAvailable;
                // this is where it will have to update the tree.
                if (curStatus!=null && !curStatus.Equals(oldStatus))
                {
                    bool emailSent = false;
                    if (curStatus != null && curStatus.IsAvaliable && (oldStatus == null || !oldStatus.IsAvaliable))
                    {
                        newlyAvailable = true;
                        if (library.generatesEmail)
                        {
                            SendEmail(MasterEmail, item.Title + " Status changed", library.Name +
                                "\r\n" + item.Title + 
                                "\r\n\r\nOld status:\r\n" + (oldStatus == null ? "[null]" : oldStatus.ToString()) + 
                                "\r\n\r\nNew status:\r\n" + curStatus + 
                                "\r\n\r\nURL: " + item.URL);
                            emailSent = true;
                        }
                    }
                    else newlyAvailable = false;
                    if ((library.IsMonitored || !item.IsAtInterestedLibrary) && !curStatus.Equals(oldStatus))
                    {
                        if (!emailSent && item.TriggersEmail)
                            SendEmail(MasterEmail, item.Title + " Status changed", library.Name +
                                "\r\n" + item.Title +
                                "\r\n\r\nOld status:\r\n" + (oldStatus == null ? "[null]" : oldStatus.ToString()) +
                                "\r\n\r\nNew status:\r\n" + curStatus +
                                "\r\n\r\nURL: " + item.URL);
#if xmpp
                        if (item.TriggersIM)
                            SendIM(MasterEmail, library.Name + "\r\n" + item.Title + "\r\n\r\nOld status:\r\n" + oldStatus + "\r\n\r\nNew status:\r\n" + curStatus + "\r\n\r\nURL: " + item.URL);
#endif
                    }
                    
                    if (oldStatus != null)
                    {
                        foreach (ItemStatus status in oldStatus.Statuses)
                        {
                            //tvLibraries
                            try
                            {
                                node = tvLibraries.Nodes[library.Name];
                                node.Nodes[status.Location].Nodes[item.URL].Remove();
                                if (node.Nodes[status.Location].Nodes.Count == 0)
                                    node.Nodes[status.Location].Remove();
                            }
                            catch (Exception e)
                            {
                                Log("Error removing \"" + status + "\" from {" + item.Title + " (" + library.Name + ")} (" + item.URL + ")\r\n   " + e);
                            }
                           
                            //tvItems
                            try
                            {
                                nodes = tvItems.Nodes.Find(item.URL, true);
                                if (nodes.Length == 0)
                                    continue;
                                node = nodes[0];
                                node.Nodes[library.Name].Remove();
                                //if (node.Nodes.Count == 0)
                                //    node.Remove();

                            }
                            catch (Exception) { }
                        }
                    }
                    if (curStatus != null)
                    {
                        foreach (ItemStatus status in curStatus.Statuses)
                        {
                            //tvLibraries
                            node = tvLibraries.Nodes[library.Name];
                            if (node == null)
                            {
                                for (i = 0; i < tvLibraries.Nodes.Count; i++)
                                {
                                    if (library.Name.CompareTo(tvLibraries.Nodes[i].Name) < 0)
                                        break;
                                }
                                node = tvLibraries.Nodes.Insert(i, library.Name, library.Name);
                            }
                            if (node.Nodes.ContainsKey(status.Location))
                                node = node.Nodes[status.Location];
                            else
                            {
                                for (i = 0; i < node.Nodes.Count; i++)
                                {
                                    if (status.Location.CompareTo(node.Nodes[i].Text) < 0)
                                        break;
                                }
                                node = node.Nodes.Insert(i, status.Location, status.Location);
                            }
                            if (!node.Nodes.ContainsKey(item.URL))
                            {
                                for (i = 0; i < node.Nodes.Count; i++)
                                {
                                    if (item.Title.ToLower().CompareTo(node.Nodes[i].Text.ToLower()) < 0)
                                        break;
                                }
                                node = node.Nodes.Insert(i, item.URL, item.Title);
                                node.ContextMenuStrip = contextMenuStripItem;
                                node.ToolTipText = curStatus.ToString();
                                node.Parent.Expand();
                                if (status.IsAvaliable)
                                    if (newlyAvailable)
                                    {
                                        node.BackColor = Color.LightGreen;//.Nodes.Add("Newly available","Newly available").Parent.Expand();
                                        //node.NodeFont = new Font(node.NodeFont, FontStyle.Bold);
                                    }
                                    else node.BackColor = Color.Empty;
                                else if (!curStatus.IsAvaliable)
                                    node.BackColor = Color.LightCoral;
                                else node.BackColor = Color.Empty;
                            }
                            else
                            {
                                node = node.Nodes[item.URL];
                                node.Text = status.Quantity.ToString() + ": " + item.Title;
                                node.ToolTipText = curStatus.ToString();
                            }

                            //tvItems
                            nodes = tvItems.Nodes.Find(item.URL, true);
                            if (nodes.Length == 0)
                            {
                                node = tvItems.Nodes[item.InList];
                                if (node == null)
                                {
                                    for (i = 0; i < tvItems.Nodes.Count; i++)
                                    {
                                        if (item.InList.CompareTo(tvItems.Nodes[i].Text) < 0)
                                            break;
                                    }
                                    node = tvItems.Nodes.Insert(i, item.InList);
                                }
                                for (i = 0; i < node.Nodes.Count; i++)
                                {
                                    //if (item.Title.CompareTo(node.Nodes[i].Text) < 0)
                                    int diff = item.Rank - Items.FromUrl(node.Nodes[i].Name).Rank;
                                    if (diff < 0 || (diff == 0 && item.Title.CompareTo(node.Nodes[i].Text) < 0))
                                        break;
                                }
                                node = node.Nodes.Insert(i, item.URL, item.ToString());
                            }
                            else
                            {
                                node = nodes[0];
                            }
                            node.ContextMenuStrip = contextMenuStripItem;
                            if (node.Parent.Text == "New Release")
                            {
                                if (node.Nodes.ContainsKey(library.Name))
                                    node = node.Nodes[library.Name];
                                else
                                {
                                    for (i = 0; i < node.Nodes.Count; i++)
                                    {
                                        if (library.Name.CompareTo(node.Nodes[i].Name) < 0)
                                            break;
                                    }
                                    node = node.Nodes.Insert(i, library.Name, library.Name);
                                }
                                if (node.Nodes.ContainsKey(status.Location))
                                    node = node.Nodes[status.Location];
                                else
                                {
                                    for (i = 0; i < node.Nodes.Count; i++)
                                    {
                                        if (status.Location.CompareTo(node.Nodes[i].Text) < 0)
                                            break;
                                    }
                                    node = node.Nodes.Insert(i, status.Location, status.Location);
                                }
                            }
                            node.ToolTipText = curStatus.ToString();
                        }
                    }
                }
                else if (oldStatus != null) // new status is the same, but we have to update the ToolTip
                {
                    node = tvLibraries.Nodes[library.Name];
                    foreach (TreeNode tn in node.Nodes.Find(item.URL, true))
                    {
                        tn.ToolTipText = curStatus.ToString();
                        if (!curStatus.IsAvaliable)
                            tn.BackColor = Color.LightCoral;
                        else tn.BackColor = Color.Empty;
                    }
                    // and now the tvItems one, (if the library is being monitored as well)
                    foreach(TreeNode n in tvItems.Nodes.Find(item.URL, true))
                    {
                        n.ContextMenuStrip = contextMenuStripItem;
                        if (n.Nodes.ContainsKey(library.Name))
                        {
                            node = n.Nodes[library.Name];
                            foreach (ItemStatus status in curStatus.Statuses)
                            {
                                if (node.Nodes.ContainsKey(status.Location))
                                {
                                    node = node.Nodes[status.Location];
                                    node.ToolTipText = curStatus.ToString();
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Log(e.ToString());
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (Libraries.Open.Count == 0)
            {
                lblStatus.Text = "All libraries are closed at the moment.";
                timer1.Enabled = true;
                return;
            }
            timer1.Enabled = false;
            ScanItem(Items.Next);
        }
#if xmpp
        private void SendIM(string to, string body)
        {
            string jid = to;
            int i = jid.IndexOf("+");
            int j = jid.IndexOf("@");
            if (i > 0)
                jid = jid.Substring(0, i) + jid.Substring(j);
            xmppClient.Send(new agsXMPP.protocol.client.Message(jid, body));
        }
#endif
        private void SendEmail(string to, string subject, string body)
        {
            if (to == null || to.Length == 0)
                return;
            System.Net.Mail.MailMessage msg = new System.Net.Mail.MailMessage();
            msg.To.Add(to);
            msg.From = new MailAddress("librarysender@gmail.com", "Library Sender", System.Text.Encoding.UTF8);
            msg.Subject = subject;
            msg.SubjectEncoding = System.Text.Encoding.UTF8;
            msg.Body = body;
            msg.BodyEncoding = System.Text.Encoding.UTF8;
            msg.IsBodyHtml = false;
            msg.Priority = MailPriority.High;

            //Add the Credentials
            SmtpClient client = new SmtpClient();
            client.Credentials = new System.Net.NetworkCredential("librarysender@gmail.com", "inconsequential");
            client.Port = 587;          
            client.Host = "smtp.gmail.com";
            client.EnableSsl = true;
            client.SendCompleted += new SendCompletedEventHandler(client_SendCompleted);
            object userState = msg;
            try
            {
                //you can also call client.Send(msg)
                client.SendAsync(msg, userState);
            }
            catch (Exception ex)
            {
                Log(ex.ToString());
            }
        }

        void client_SendCompleted(object sender, AsyncCompletedEventArgs e)
        {
            //TODO: possibly do something here.
        }

        private void fileSystemWatcher1_Changed(object sender, FileSystemEventArgs e)
        {
            if (e.Name.ToLower() == "email.txt")
            {
                LoadEmail();
            }
            else if (e.Name.Contains("url"))
            {
                try
                {
                    StreamReader sr = new StreamReader(e.FullPath);
                    string url = sr.ReadToEnd().Trim();
                    sr.Close();
                    File.Delete(e.FullPath);
                    if (url.StartsWith("http://catalog.cincinnatilibrary.org/uhtbin/ckey/") && !Items.ContainsURL(url))
                    {
                        Items.Add(url);
                        Items.Save();
                        UpdateItemCount();
                        if ((!firstPass && !working) || Libraries.Open.Count==0)
                        {
                            timer1.Enabled = false;
                            ScanItem(Items.Next);
                        }
                    }
                }
                catch (IOException) { }
                catch (Exception ex)
                {
                    Log(ex.ToString());
                }
            }
        }

        private void treeView_MouseClick(object sender, MouseEventArgs e)
        {
            if (sender.GetType() != typeof(TreeView))
                return;
            TreeView tv = (TreeView)sender;
            TreeNode node = ActiveNode = tv.GetNodeAt(e.Location);
            if (node == null)
                return;
            switch (e.Button)
            {
                case MouseButtons.Right:
                    if (node.Name.StartsWith("http:"))
                    {
                        miRemove.Text = "Remove " + node.Name;
                        miRefresh.Text = "Refresh " + node.Text;
                        try
                        {
                            LibraryItem item = Items.FromUrl(ActiveNode.Name);
                            if (item == null)
                            {
                                miTriggerEmail.Enabled = false;
                                miTriggerEmail.Checked = false;
                                miTriggerIM.Enabled = false;
                                miTriggerIM.Checked = false;
                            }
                            else
                            {
#if xmpp
                                miTriggerIM.Enabled = true;
                                miTriggerIM.Checked = item.TriggersIM;
#else
                                miTriggerIM.Enabled = false;
                                miTriggerIM.Checked = false;
#endif
                                miTriggerEmail.Enabled = true;
                                miTriggerEmail.Checked = item.TriggersEmail;
                            }
                        }
                        catch (Exception ex)
                        {
                            Log(ex.ToString());
                            miTriggerEmail.Enabled = false;
                            miTriggerIM.Enabled = false;
                            miTriggerEmail.Checked = false;
                            miTriggerIM.Checked = false;
                        }
                    }
                    break;
            }
        }

        private void treeView_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (sender.GetType() != typeof(TreeView))
                return;
            TreeView tv = (TreeView)sender;
            TreeNode node = tv.GetNodeAt(e.Location);
            if (node == null)
                return;
            if (node.Name.StartsWith("http:"))
            {
                //System.Diagnostics.Process p = new System.Diagnostics.Process();
                //p.StartInfo = new System.Diagnostics.ProcessStartInfo(node.Name);
                //p.Start();
                webBrowserSearch.Navigate(node.Name);
                tabControl1.SelectTab(tabSearch);
            }
        }

        private void Log(string text)
        {
            using (StreamWriter sw = new StreamWriter("Library.log", true))
            {
                sw.WriteLine(DateTime.Now.ToShortDateString() + " - " + DateTime.Now.ToLongTimeString() + " - " + text);
            }
        }

        private void miRemove_Click(object sender, EventArgs e)
        {
            TreeNode node = ActiveNode;
            if (node == null)
                return;
            Items.Remove(node.Name);
            RemoveUrlFromTreeViews(node.Name);
            Items.Save();
            UpdateItemCount();
        }

        private void RemoveUrlFromTreeViews(string url)
        {
            foreach (TreeNode n in tvLibraries.Nodes.Find(url, true))
            {
                if (n.Parent.Nodes.Count == 1)
                {
                    if (n.Parent.Parent.Nodes.Count == 1)
                        n.Parent.Parent.Remove();
                    else n.Parent.Remove();
                }
                else n.Remove();
            }
            foreach (TreeNode n in tvItems.Nodes.Find(url, true))
            {
                n.Remove();
            }
        }

        private void miRefresh_Click(object sender, EventArgs e)
        {
            if (firstPass)
                return;
            TreeNode node = ActiveNode;
            if (node == null)
                return;
            if (!firstPass && node.Name.StartsWith("http:"))
            {
                timer1.Enabled = false;
                working = true;
                foreach (TreeNode n in tvItems.Nodes.Find(node.Name, true))
                    n.BackColor = Color.Yellow;
                foreach (TreeNode n in tvLibraries.Nodes.Find(node.Name, true))
                {
                    n.BackColor = Color.Yellow;
                }
                lblStatus.Text = "Navigating to " + Items.FromUrl(node.Name);
                webBrowser1.Navigate(node.Name);
            }
        }

        private void webBrowser2_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            Visible = setVisible;
            HtmlDocument document = webBrowser2.Document;
            string url = document.Url.ToString();
            if (url == "http://www.cincinnatilibrary.org/info/hours.asp")
            {
                Libraries.LoadFrom(document);
                listIgnoredLibraries.Items.AddRange(Libraries.Ignored.ToArray());
                cListMonitoredLibraries.Items.AddRange(Libraries.Monitored.ToArray());
                foreach (Library l in Libraries.ThatGenerateEmail)
                {
                    int i = cListMonitoredLibraries.Items.IndexOf(l);
                    if (i >= 0)
                        cListMonitoredLibraries.SetItemChecked(i, true);
                }
                return;
            }
            if (document.GetElementById("searchsummary") != null)
            {
                string browseSubjectsURL = null;
                foreach (HtmlElement a in document.GetElementsByTagName("a"))
                {
                    if (a.InnerText == "URL for this Item")
                    {
                        browseSubjectsURL = a.GetAttribute("href");
                        System.Diagnostics.Process p = new System.Diagnostics.Process();
                        p.StartInfo = new System.Diagnostics.ProcessStartInfo(a.GetAttribute("href"));
                        p.Start();
                        return;   
                    }
                    if (a.InnerText == "Browse Subjects")
                    {
                        browseSubjectsURL = a.GetAttribute("href");
                    }
                }
                if (browseSubjectsURL != null)
                {
                    webBrowser2.Navigate(browseSubjectsURL);
                    return;
                }
            }
            else if (document.GetElementById("searchform") == null)
            {
                string href = "";
                foreach (HtmlElement a in document.GetElementsByTagName("a"))
                {
                    if (a.GetAttribute("title") == "Go Back")
                    {
                        href = a.GetAttribute("href");
                        System.Diagnostics.Process p = new System.Diagnostics.Process();
                        p.StartInfo = new System.Diagnostics.ProcessStartInfo(a.GetAttribute("href"));
                        p.Start();
                        return;
                    }
                }
                return;
            }
            HtmlElement searchform = document.GetElementById("searchform");
            searchform.SetAttribute("method", "get");
            string URL = document.Url.OriginalString.Substring(0, document.Url.OriginalString.IndexOf(document.Url.PathAndQuery)) + searchform.GetAttribute("action");
            //string URL = document.Url.Host + searchform.GetAttribute("action");
            URL += "?searchdata1=" + QueryString + "&srchfield1=TI^TITLE^SERIES^Title+Processing^title&library=ALL&item_1cat=VIDEO-DVD&item_2cat=ANY&pubyear=&sort_by=ANY";
            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.StartInfo = new System.Diagnostics.ProcessStartInfo(URL);
            proc.Start();
            return;
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            SaveState();
            timer1.Enabled = false;
            Settings.SaveToFile("settings.conf");
            Settings.RemoveAllHotKeys();
            Items.Save();
            Libraries.Save();
#if xmpp
            if (xmppClient.Authenticated)
            {
                xmppClient.Close();
            }
#endif
        }

        private void Form1_Load(object sender, EventArgs e)
        {
#if xmpp
            xmppClient.Open();
#endif
            UpdateItemCount();
            ScanItem(Items.Next);
            Visible = setVisible;
        }

        protected override void WndProc(ref System.Windows.Forms.Message m)
        {
            // let the base class process the message
            base.WndProc(ref m);

            // if this is a WM_HOTKEY message, notify the parent object
            const int WM_HOTKEY = 0x312;
            switch (m.Msg)
            {
                case WM_HOTKEY:
                    setVisible = 
                        TopMost = 
                        Visible = !Visible;
                    if (TopMost)
                        Activate();
                    break;
            }
        }

        private void btnListen_Click(object sender, EventArgs e)
        {
            KeyListening = true;
            if (KeyListening)
            {
                NewHotKey = new HotKey(Keys.None, KeyMod.None, Handle);
                KeyDown += new KeyEventHandler(Form1_KeyDown);
                KeyUp += new KeyEventHandler(Form1_KeyUp);
                txtHotKey.Text = "listening for keyboard input...";
                btnListen.Enabled = false;
            }
        }

        void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == (Keys.RButton | Keys.ShiftKey))
                NewHotKey.Modifiers |= KeyMod.Alt;
            else if (e.KeyCode == (Keys.LButton | Keys.ShiftKey))
                NewHotKey.Modifiers |= KeyMod.Control;
            else if (e.KeyCode == Keys.ShiftKey)
                NewHotKey.Modifiers |= KeyMod.Shift;
            else if (e.KeyCode == Keys.LWin || e.KeyCode == Keys.RWin)
                NewHotKey.Modifiers |= KeyMod.Win;
            else
            {
                NewHotKey.Key = e.KeyCode;
            }
            txtHotKey.Text = NewHotKey.ToString();
        }

        void Form1_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == (Keys.RButton | Keys.ShiftKey))
                NewHotKey.Modifiers &= ~KeyMod.Alt;
            else if (e.KeyCode == (Keys.LButton | Keys.ShiftKey))
                NewHotKey.Modifiers &= ~KeyMod.Control;
            else if (e.KeyCode == Keys.ShiftKey)
                NewHotKey.Modifiers &= ~KeyMod.Shift;
            else if (e.KeyCode == Keys.LWin || e.KeyCode == Keys.RWin)
                NewHotKey.Modifiers &= ~KeyMod.Win;
            else if (NewHotKey.Key== e.KeyCode)
            {
                //txtKey.Text = "";
                //key = Keys.None;
                KeyListening = false;
                KeyDown -= Form1_KeyDown;
                KeyUp -= Form1_KeyUp;
                btnListen.Enabled = true;
                btnAddHotKey.Enabled = true;
            }
            txtHotKey.Text = NewHotKey.ToString();
        }

        private void btnAddHotKey_Click(object sender, EventArgs e)
        {
            if (NewHotKey != null && NewHotKey.Key != Keys.None)
            {
                Settings.AddHotKey(NewHotKey);
                btnAddHotKey.Enabled = false;
                txtHotKey.Text = "To add a hotkey, press \"Listen\"";
            }
        }

        private void listHotKeys_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete)
            {
                Settings.RemoveHotKey((HotKey)listHotKeys.SelectedItem);
            }
        }

        private void btnAddURL_Click(object sender, EventArgs e)
        {
            string s = txtURL.Text.Trim();
            if (s.StartsWith("http://") && !Items.ContainsURL(s) && s.Contains("/uhtbin/ckey/"))
            {
                Items.Add(s);
                Items.Save();
                UpdateItemCount();
                if (!firstPass && !working)
                {
                    timer1.Enabled = false;
                    ScanItem(Items.Next);
                }
                txtURL.Text = "";
            }
        }

        private void UpdateItemCount()
        {
            ItemCount = Items.TotalCount;
        }

        private void listLibraries_DoubleClick(object sender, EventArgs e)
        {
            if (listIgnoredLibraries.SelectedItem == null)
                return;
            btnMonitorSelected_Click(sender, e);
        }

        private void txtURL_Enter(object sender, EventArgs e)
        {
            AcceptButton = btnAddURL;
        }

        private void txtURL_Leave(object sender, EventArgs e)
        {
            AcceptButton = null;
        }

        private void btnMonitorSelected_Click(object sender, EventArgs e)
        {
            if (listIgnoredLibraries.SelectedItems != null && listIgnoredLibraries.SelectedItems.Count > 0)
            {
                Library[] libs = new Library[listIgnoredLibraries.SelectedItems.Count];
                listIgnoredLibraries.SelectedItems.CopyTo(libs,0);
                foreach (Library l in libs)
                {
                    l.IsMonitored = true;
                    l.generatesEmail = false;
                    cListMonitoredLibraries.Items.Add(l);
                    listIgnoredLibraries.Items.Remove(l);
                }
                foreach (LibraryItem item in Items.All)
                {
                    foreach (Library lib in libs)
                    {
                        if (item.StatusAt.ContainsKey(lib))
                            UpdateTree(null, item, lib);
                    }
                    UpdateAllRanks();
                }
                Items.UpdateMonitoredItems();
            }
        }

        private void btnMonitorAll_Click(object sender, EventArgs e)
        {
            Library[] libs = new Library[listIgnoredLibraries.Items.Count];
            listIgnoredLibraries.Items.CopyTo(libs,0);
            foreach (Library l in libs)
            {
                l.IsMonitored = true;
                l.generatesEmail = false;
                cListMonitoredLibraries.Items.Add(l);
                listIgnoredLibraries.Items.Remove(l);
            }
            foreach (LibraryItem item in Items.All)
            {
                foreach (Library lib in libs)
                {
                    if (item.StatusAt.ContainsKey(lib))
                        UpdateTree(null, item, lib);
                }
            }
            UpdateAllRanks();
            Items.UpdateMonitoredItems();
        }

        private void btnIgnoreSelected_Click(object sender, EventArgs e)
        {
            if (cListMonitoredLibraries.SelectedItem != null)
            {
                Library newlyIgnored = ((Library)cListMonitoredLibraries.SelectedItem);
                newlyIgnored.IsMonitored = false;
                newlyIgnored.generatesEmail = false;
                listIgnoredLibraries.Items.Add(newlyIgnored);
                cListMonitoredLibraries.Items.Remove(newlyIgnored);
                foreach (TreeNode node in tvLibraries.Nodes.Find(newlyIgnored.Name, false))
                {
                    node.Remove();
                }
                foreach (TreeNode node in tvItems.Nodes.Find(newlyIgnored.Name, true))
                {
                    node.Remove();
                }
                Items.UpdateMonitoredItems();
            }
        }

        private void btnIgnoreAll_Click(object sender, EventArgs e)
        {
            Library[] libs = new Library[cListMonitoredLibraries.Items.Count];
            cListMonitoredLibraries.Items.CopyTo(libs,0);
            foreach (Library l in libs)
            {
                l.IsMonitored = false;
                l.generatesEmail = false;
                listIgnoredLibraries.Items.Add(l);
                cListMonitoredLibraries.Items.Remove(l);
                foreach(TreeNode node in tvLibraries.Nodes.Find(l.Name, false))
                {
                    node.Remove();
                }
                foreach (TreeNode node in tvItems.Nodes.Find(l.Name, true))
                {
                    node.Remove();
                }
                Items.UpdateMonitoredItems();
            }
        }

        private void cListMonitoredLibraries_ItemCheck(object sender, ItemCheckEventArgs e)
        {
            Library l = (Library)cListMonitoredLibraries.Items[e.Index];
            if (e.NewValue == CheckState.Checked)
                l.generatesEmail = true;
            else l.generatesEmail = false;
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            // Save any settings that have changed
            // Save Email
            if (txtEmail.Text != MasterEmail)
            {
                MasterEmail = txtEmail.Text;
                fileSystemWatcher1.EnableRaisingEvents = false;
                using (StreamWriter sw = new StreamWriter("email.txt", false))
                {
                    sw.Write(MasterEmail);
                }
                fileSystemWatcher1.EnableRaisingEvents = true;
#if xmpp
                try
                {
                    xmppClient.Resource = MasterEmail.Substring(0, MasterEmail.IndexOf('@'));
                    xmppClient.Close();
                }
                catch (Exception ex) { Log(ex.ToString()); }
#endif
            }
            // Save settings
            Settings.SaveToFile("settings.conf");
            // Save Library info
            Libraries.Save();
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            LoadEmail();
            Settings.RemoveAllHotKeys();
            Settings.LoadFromFile("settings.conf", Handle);
            Libraries.ReLoad();
            listIgnoredLibraries.Items.Clear();
            listIgnoredLibraries.Items.AddRange(Libraries.Ignored.ToArray());
            cListMonitoredLibraries.Items.Clear();
            cListMonitoredLibraries.Items.AddRange(Libraries.Monitored.ToArray());
            numHolds.Value = Settings.NumHolds;
            foreach (Library l in Libraries.ThatGenerateEmail)
            {
                int i = cListMonitoredLibraries.Items.IndexOf(l);
                if (i >= 0)
                    cListMonitoredLibraries.SetItemChecked(i, true);
            }
        }

        private void webBrowserSearch_ProgressChanged(object sender, WebBrowserProgressChangedEventArgs e)
        {
            //if (searchPageAltered)
              //  return;
            string href;
            // look for the "URL for this item" link and add another link to add the URL to our list
            foreach (HtmlElement a in webBrowserSearch.Document.GetElementsByTagName("a"))
            {
                if (a.GetAttribute("modified").Length > 0)
                    return;
                href = a.GetAttribute("href");
                if (a.InnerText == "URL for this Item" && href.Contains("/uhtbin/ckey/"))
                {
                    a.SetAttribute("modified", "true");
                    string description;
                    string link;
                    if (Items.ContainsURL(href))
                    {
                        description = "Stop Monitoring this item";
                        link = "javascript:void(\"RemoveURL\",\"" + a.GetAttribute("href") + "\");";
                    }
                    else
                    {
                        description = "Monitor this item";
                        link = "javascript:void(\"AddURL\",\"" + a.GetAttribute("href") + "\");";
                    }
                    if (a.Parent.TagName.ToLower() == "li")
                    {
                        a.Parent.OuterHtml += "<li><a href='" + link + "'>" + description + "</a></li>";
                    }
                    else
                        a.OuterHtml += " <a href='" + link + "'>(" + description + ")</a>";
                    return;
                }
            }
        }

        private void webBrowserSearch_Navigating(object sender, WebBrowserNavigatingEventArgs e)
        {
            string url = e.Url.ToString();
            if(url.StartsWith("javascript:updatekeptlist("))
            {
                url = "http://catalog.cincinnatilibrary.org/uhtbin/ckey/" +
                    url.Substring(27, url.IndexOf('\'', 27) - 27);
                if(Items.ContainsURL(url))
                    Items.Remove(url);
                else
                    ScanWebPage(webBrowserSearch.Document, url);
                Items.Save();
                UpdateItemCount();
            }
            else if (!url.StartsWith("javascript:void(\""))
                return;
            if (url.StartsWith("javascript:void(\"RemoveURL\",\""))
            {
                url = url.Substring(29, url.IndexOf('\"', 29) - 29);
                Items.Remove(url);
                Items.Save();
                RemoveUrlFromTreeViews(url);
                UpdateItemCount();
                foreach (HtmlElement a in webBrowserSearch.Document.GetElementsByTagName("a"))
                {
                    string href = a.GetAttribute("href");
                    if (href == e.Url.ToString())
                    {
                        a.SetAttribute("href", "javascript:void(\"AddURL\",\"" + url + "\");");
                        if (a.InnerText[0] == '(')
                            a.InnerText = "(Monitor this item)";
                        else a.InnerText = "Monitor this item";
                    }
                }
            }
            if (url.StartsWith("javascript:void(\"AddURL\",\""))
            {
                url = url.Substring(26,url.IndexOf('\"',26)-26);
//                url = url.Substring(0,url.IndexOf('\"'));
                if (!Items.ContainsURL(url))
                {
                    ScanWebPage(webBrowserSearch.Document,url);
                    Items.Save();
                    foreach (HtmlElement a in webBrowserSearch.Document.GetElementsByTagName("a"))
                    {
                        string href = a.GetAttribute("href");
                        if (href==e.Url.ToString())
                        {
                            a.SetAttribute("href", "javascript:void(\"RemoveURL\",\"" + url + "\");");
                            if (a.InnerText[0]=='(')
                                a.InnerText = "(Stop Monitoring this item)";
                            else a.InnerText = "Stop Monitoring this item";
                        }
                    }
                }
            }
        }
        private void timer2_Tick(object sender, EventArgs e)
        {
#if xmpp
            try
            {
                xmppClient.Open();
            }
            catch (Exception) { }
#endif
        }
        private void treeView_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            TreeView tv = sender as TreeView;
            if(tv == null)
                return;
            if (e.KeyCode == Keys.Apps)
            {
                ActiveNode = tv.SelectedNode;
                if (ActiveNode!=null && ActiveNode.Name.StartsWith("http:"))
                {
                    miRemove.Text = "Remove " + ActiveNode.Name;
                    miRefresh.Text = "Refresh " + ActiveNode.Text;
                    try
                    {
                        LibraryItem item = Items.FromUrl(ActiveNode.Name);
                        if (item == null)
                        {
                            miTriggerIM.Checked = false;
                            miTriggerIM.Enabled = false;
                            miTriggerEmail.Enabled = false;
                            miTriggerEmail.Checked = false;
                        }
                        else
                        {
#if xmpp
                            miTriggerIM.Enabled = true;
                            miTriggerIM.Checked = item.TriggersIM;
#else
                            miTriggerIM.Checked = false;
                            miTriggerIM.Enabled = false;
#endif
                            miTriggerEmail.Enabled = true;
                            miTriggerEmail.Checked = item.TriggersEmail;
                        }
                    }
                    catch (Exception ex)
                    {
                        Log(ex.ToString());
                        miTriggerEmail.Enabled = false;
                        miTriggerIM.Enabled = false;
                        miTriggerEmail.Checked = false;
                        miTriggerIM.Checked = false;
                    }
                }
            }
        }

        private void miTriggerEmail_Click(object sender, EventArgs e)
        {
            if (ActiveNode == null)
                return; 
            LibraryItem item = Items.FromUrl(ActiveNode.Name);
            item.TriggersEmail = miTriggerEmail.Checked;
        }

        private void miTriggerIM_Click(object sender, EventArgs e)
        {
            if (ActiveNode == null)
                return;
            LibraryItem item = Items.FromUrl(ActiveNode.Name);
            item.TriggersIM = miTriggerIM.Checked;
        }

        private void btnResetStatistics_Click(object sender, EventArgs e)
        {
            foreach (LibraryItem i in Items.All)
            {
                foreach (ItemStatus s in i.StatusAt.Values)
                {
                    s.ResetStatistics();
                }
            }
        }

        private void webBrowserSearch_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            Visible = setVisible;
        }

        private void timer3_Tick(object sender, EventArgs e)
        {
            timer3.Enabled = false;
            try
            {
                //Log("Web page for " + Items.Current + " didn't load after 10 seconds.  Moving on to the next item.");
            }
            catch { }
            try
            {
                ScanItem(Items.Next);
            }
            catch (Exception ex)
            {
                Log(ex.ToString());
            }
        }

        private void treeView_KeyUp(object sender, KeyEventArgs e)
        {
            if(e.KeyCode == Keys.Delete)
            {
                TreeNode node = (sender as TreeView).SelectedNode;
                if(node == null || node.Name == node.Text)
                    return;
                Items.Remove(node.Name);
                RemoveUrlFromTreeViews(node.Name);
                Items.Save();
                UpdateItemCount();
            }
        }

        private void Form1_Deactivate(object sender, EventArgs e)
        {
#if !DEBUG
            setVisible = TopMost = Visible = Settings.HotKeys.Count == 0 ?
                                                 true :
                                                 false;
#endif
        }

        private void webBrowser3_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            HtmlDocument doc = (sender as WebBrowser).Document;
            switch (wb3state)
            {
                case "":
                    {
                        HtmlElement userID = doc.GetElementById("user_id");
                        if (userID == null)
                        {
                            if (e.Url.AbsoluteUri != "http://catalog.cincinnatilibrary.org/")
                                Log("no element found on homepage with 'user_id' id");
                            return;
                        }
                        userID.InnerText = USERID;
                        HtmlElement password = doc.GetElementById("password");
                        password.InnerText = USERPIN;
                        doc.GetElementById("loginform").InvokeMember("submit");
                        wb3state = "logged in";
                        break;
                    }
                case "logged in":
                    {
                        HtmlElement userName = doc.GetElementById("userName");
                        if (userName == null)
                        {
                            Log("Failed to login as " + USERID);
                            return;
                        }
                        try
                        {
                            //if (userName.InnerText.StartsWith("you are logged in as"))
                            //    user.name = userName.InnerText.Substring(20).Trim();
                            //if (user.name.Contains(","))
                            //    user.name = user.name.Substring(user.name.IndexOf(",") + 1).Trim() + " " + user.name.Substring(0, user.name.IndexOf(",")).Trim();
                        }
                        catch (Exception ex)
                        {
                            Log("Exception thrown: " + ex.ToString());
                        }
                        foreach (HtmlElement a in doc.GetElementsByTagName("a"))
                        {
                            if (a.InnerText == "My Account")
                            {
                                wb3state = "my account";
                                (sender as WebBrowser).Navigate(a.GetAttribute("href"));
                                return;
                            }
                        }
                        Log("No 'My Account' link found on page after logging in as " + USERID); 
                        break;
                    }
                case "my account":
                    {
                        foreach (HtmlElement a in doc.GetElementsByTagName("a"))
                        {
                            if (a.InnerText == "Review My Account")
                            {
                                wb3state = "review";
                                (sender as WebBrowser).Navigate(a.GetAttribute("href"));
                                return;
                            }
                        }
                        Log("No 'Renew My Materials' link found after clicking 'My Account' link");
                        break;
                    }
                case "review":
                    {
                        HtmlElement userInfo = doc.GetElementById("userinfoscreen");
                        List<string> itemsOnHold = new List<string>();
                        List<string> currentCheckouts = new List<string>();
                        int holds = 0;
                        if (userInfo != null)
                        {
                            HtmlElementCollection tags = userInfo.GetElementsByTagName("th");
                            HtmlElement th = null;
                            foreach (HtmlElement el in tags)
                            {
                                if (el.InnerText == "Holds")
                                {
                                    th = el;
                                    break;
                                }
                            }
                            HtmlElement td = th.NextSibling;
                            int.TryParse(td.InnerText, out holds);
                        }
                        HtmlElement holdstable = doc.GetElementById("holds");
                        {
                            LibraryItem item;
                            if (holdstable != null)
                            {
                                List<LibraryItem> oldHolds = Items.All.FindAll(li => li.Status == Status.OnHold || li.Status == Status.ReadyForPickup);
                                foreach (HtmlElement tr in holdstable.GetElementsByTagName("tr"))
                                {
                                    string title = null;
                                    foreach (HtmlElement el in tr.GetElementsByTagName("label"))
                                    {
                                        title = el.InnerText;
                                    }
                                    if(string.IsNullOrEmpty(title))
                                        continue;
                                    if (Items.ContainsTitle(title))
                                    {
                                        HtmlElementCollection cells = tr.GetElementsByTagName("td");

                                        itemsOnHold.Add(title);
                                        item = Items.All.Find(m => m.Title == title);
                                        item.Status = cells[3].InnerText.Trim().ToLower() == "unavailable"
                                                    ? Status.OnHold
                                                    : Status.ReadyForPickup;
                                        UpdateTree(null, item, null);
                                        if (oldHolds.Contains(item))
                                            oldHolds.Remove(item);
                                    }
                                }
                                foreach (LibraryItem i in oldHolds)
                                {
                                    i.Status = Status.Default;
                                    UpdateTree(null, i, null);
                                }
                            }
                            HtmlElement checkouts = doc.GetElementById("charges");
                            if (checkouts != null)
                            {
                                List<LibraryItem> oldCharges = Items.All.FindAll(li => li.Status == Status.CheckedOutToMe);
                                foreach (HtmlElement el in checkouts.GetElementsByTagName("span"))
                                {
                                    string title = el.InnerText;
                                    if (Items.ContainsTitle(title))
                                    {
                                        currentCheckouts.Add(title);
                                        item = Items.All.Find(m => m.Title == title);
                                        item.Status = Status.CheckedOutToMe;
                                        UpdateTree(null, item, null);
                                        if (oldCharges.Contains(item))
                                            oldCharges.Remove(item);
                                    }
                                }
                                foreach (LibraryItem i in oldCharges)
                                {
                                    i.Status = Status.Default;
                                    UpdateTree(null, i, null);
                                }
                            }
                        }
                        if (itemsOnHold.Count < Settings.NumHolds && holds < 25)
                        {
                            // put some more on hold...
                            List<LibraryItem> sortedList = Items.ToBePutOnHold;
                            foreach (LibraryItem item in sortedList)
                            {
                                if (itemsOnHold.Contains(item.Title) || currentCheckouts.Contains(item.Title))
                                    continue;
                                itemsToGet.Enqueue(item);
                                if (itemsToGet.Count + itemsOnHold.Count >= Settings.NumHolds)
                                    break;
                            }
                            wb3state = "tryhold";
                            if (itemsToGet.Count > 0)
                            {
                                LibraryItem current = itemsToGet.Dequeue();
                                (sender as WebBrowser).Navigate(current.URL);
                            }
                        }
                        UpdateAllRanks();
                        break;
                    }
                case "tryhold":
                    {
                        HtmlElement content = doc.GetElementById("contentholder");
                        wb3state = "holdform";
                        foreach (HtmlElement el in content.GetElementsByTagName("a"))
                        {
                            if (el.InnerText == "Place Hold")
                                (sender as WebBrowser).Navigate(el.GetAttribute("href"));
                        }
                        break;
                    }
                case "holdform":
                    {
                        wb3state = "nexthold";
                        HtmlElement holdform = doc.GetElementById("holdform");
                        holdform.InvokeMember("submit");
                        break;
                    }
                case "nexthold":
                    {
                        wb3state = "tryhold";
                        if (itemsToGet.Count == 0)
                            return;
                        LibraryItem current = itemsToGet.Dequeue();
                        (sender as WebBrowser).Navigate(current.URL);
                        break;
                    }
            }
        }

        private void tvItems_AfterCheck(object sender, TreeViewEventArgs e)
        {
            TreeView tv = sender as TreeView;
            tv.AfterCheck -= tvItems_AfterCheck;
            LibraryItem item = Items.FromUrl(e.Node.Name);
            if (item != null)
                item.PutOnHold = e.Node.Checked;
            foreach (TreeNode n in e.Node.Nodes)
            {
                n.Checked = e.Node.Checked;
            }
            if(e.Action != TreeViewAction.Collapse)
                for (var ParentNode = e.Node.Parent; ParentNode != null; ParentNode = ParentNode.Parent)
                {
                    bool test = ParentNode.Nodes.Cast<TreeNode>().All(n => n.Checked);
                    if (test != ParentNode.Checked)
                    {
                        ParentNode.Checked = test;
                    }
                }
            foreach (TreeNode n in e.Node.Nodes)
            {
                tvItems_AfterCheck(sender, new TreeViewEventArgs(n, TreeViewAction.Collapse));
                tv.AfterCheck -= tvItems_AfterCheck;
            }
            tv.AfterCheck += tvItems_AfterCheck;
        }

        private void tvItems_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
        {
            e.Node.Name = e.Label;
            foreach (LibraryItem i in Items.All.FindAll(i => i.InList == e.Node.Text))
            {
                i.InList = e.Label;
            }
        }

        private void numHolds_ValueChanged(object sender, EventArgs e)
        {
            Settings.NumHolds = Convert.ToInt32((sender as NumericUpDown).Value);
        }

        private void Form1_KeyDown_1(object sender, KeyEventArgs e)
        {
            if (e.KeyData == (Keys.Control | Keys.H))
            {
                TrySomeHolds(null);
            }
        }

        private void treeView_ItemDrag(object sender, ItemDragEventArgs e)
        {
            TreeView tv = sender as TreeView;
            if (tv == null)
                return;
            TreeNode node = e.Item as TreeNode;
            if (node == null)
                return;
            string url = node.Name;
            if (!url.StartsWith("http://catalog.cincinnatilibrary.org/uhtbin/ckey/"))
            {
                if ((node.Text.IsSpecialStatus() && node.Parent == null) || node.Nodes.Count == 0)
                    return;
                if (node.Nodes[0].Name.StartsWith("http://catalog.cincinnatilibrary.org/uhtbin/ckey/"))
                    url = string.Join("\n", node.Nodes.Cast<TreeNode>().Select(a => a.Name).ToArray());
            }
            TreeNode FirstParent = node;
            while (FirstParent.Parent != null)
                FirstParent = FirstParent.Parent;
            tv.DoDragDrop(url,
                FirstParent.Text.IsSpecialStatus() ? DragDropEffects.Copy : DragDropEffects.Move
                );
        }

        private void Form1_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.Text, false))
            {
                string text = (string)e.Data.GetData(DataFormats.Text);
                if (text.StartsWith("http://catalog.cincinnatilibrary.org/uhtbin/ckey/"))
                {
                    if (Items.ContainsURL(text))
                        e.Effect = DragDropEffects.Copy;
                    else e.Effect = DragDropEffects.Link;
                }
                else e.Effect = DragDropEffects.None;
            }
        }

        private void Form1_DragDrop(object sender, DragEventArgs e)
        {
            string data;
            if (e.Data.GetDataPresent(DataFormats.Html))
                data = e.Data.GetData(DataFormats.Html) as string;
            else data = e.Data.GetData(DataFormats.Text) as string;
            MatchCollection matches = Regex.Matches(data, "http://catalog\\.cincinnatilibrary\\.org/uhtbin/ckey/\\d+");
            int countNew = 0;
            string url = "";
            foreach (Match m in matches)
            {
                url = m.Value;
                if (url.StartsWith("http://catalog.cincinnatilibrary.org/uhtbin/ckey/") && !Items.ContainsURL(url))
                {
                    if (AddOrMoveUrl(url, "Default", -1))
                        countNew++;
                }
            }
            if (countNew > 0)
            {
                if (!firstPass && !working)
                {
                    timer1.Enabled = false;
                    ScanItem(Items.Next);
                }
            }
            try
            {
                tvItems.SelectedNode = tvItems.Nodes.Find(url, true).First();
            }
            catch { }
            UpdateAllRanks();
        }

        private bool AddOrMoveUrl(string url, string category, int newRank)
        {
            LibraryItem item;
            if (Items.ContainsURL(url))
            {
                item = Items.FromUrl(url);
                item.InList = category;
                item.Rank = newRank;
                Items.Save();
                foreach (TreeNode n in tvItems.Nodes.Find(url, true))
                {
                    n.Remove();
                }
                UpdateTree(null, item, null);
                return false;
            }
            else
            {
                item = Items.Add(url);
                item.InList = category;
                Items.Save();
                UpdateTree(null, item, null);
                UpdateItemCount();
                return true;
            }
        }

        private void treeView_DragDrop(object sender, DragEventArgs e)
        {
            string data;
            int index;
            TreeView tv = sender as TreeView;
            if (tv == null)
                return;
            if (e.Data.GetDataPresent(DataFormats.Html))
                data = e.Data.GetData(DataFormats.Html) as string;
            else data = e.Data.GetData(DataFormats.Text) as string;
            MatchCollection matches = Regex.Matches(data, "http://catalog\\.cincinnatilibrary\\.org/uhtbin/ckey/\\d+",RegexOptions.RightToLeft);
            string url = "";
            int countNew = 0;
            List<string> finished = new List<string>();

            Point pt = tv.PointToClient(new Point(e.X, e.Y));
            TreeNode droppedNode = tv.GetNodeAt(pt);
            if (droppedNode != null)
            {
                //int level = 0;
                //for(TreeNode node = droppedNode.Parent; node!= null; node = node.Parent)
                //{
                //    level++;
                //}

                TreeNode droppedFirstParent = droppedNode;
                TreeNode droppedSecondParent = null;
                while (droppedFirstParent.Parent != null)
                    droppedFirstParent = (droppedSecondParent = droppedFirstParent).Parent;
                string category = droppedFirstParent.Text;
                index = 0;
                if (category.IsSpecialStatus() && droppedSecondParent != null)
                {
                    category = droppedSecondParent.Text;
                    if (droppedNode == droppedSecondParent)
                        index = int.MinValue;
                }
                else
                {
                    if (droppedNode == droppedFirstParent)
                        index = int.MinValue;
                }
                if (index == 0)
                {
                    index = droppedNode.Index * 2 - 1;
                    if (Items.ContainsURL(url))
                    {
                        LibraryItem item = Items.FromUrl(url);
                        LibraryItem droppedItem = Items.FromUrl(droppedNode.Name);
                        if (item.InList == category && droppedItem.Rank > item.Rank)
                            index += 2;
                    }
                }

                foreach (Match m in matches)
                {
                    url = m.Value;
                    if (!url.StartsWith("http://catalog.cincinnatilibrary.org/uhtbin/ckey/") || finished.Contains(url, StringComparer.InvariantCultureIgnoreCase))
                        continue;
                    finished.Add(url);
                    if (AddOrMoveUrl(url, category, index))
                        countNew++;
                    //TreeNode draggedFirstParent = draggedNode;
                    //while (draggedFirstParent.Parent != null)
                    //    draggedFirstParent = draggedFirstParent.Parent;
                    //int index = 0;
                    //if (droppedNode != droppedFirstParent)
                    //{
                    //    // wasn't dropped onto its own list heading...
                    //    if (draggedFirstParent != droppedFirstParent)
                    //    {
                    //        // within a single list, an item was dragged down
                    //        index = droppedNode.Index + 1;
                    //    }
                    //    else 
                    //        index = droppedNode.Index;
                    //}

                    //draggedNode.Remove();
                    //droppedFirstParent.Nodes.Insert(index, draggedNode);
                    //tv.SelectedNode = draggedNode;
                    //UpdateRanks(droppedFirstParent, 0);
                }
            }
            if (countNew > 0)
            {
                if (!firstPass && !working)
                {
                    timer1.Enabled = false;
                    ScanItem(Items.Next);
                }
            }
            try
            {
                tvItems.SelectedNode = tvItems.Nodes.Find(url, true).First();
            }
            catch { }
            UpdateAllRanks();
        }

        private void UpdateAllRanks()
        {
            int offset;
            foreach (TreeNode n in tvItems.Nodes)
            {
                switch (n.Text.ToStatusEnum())
                {
                    case Status.NewRelease:
                    case Status.OnHold:
                    case Status.ReadyForPickup:
                        offset = -1000;
                        break;
                    case Status.CheckedOutToMe:
                        offset = 1000;
                        break;
                    default:
                        offset = 0;
                        break;
                }
                UpdateRanks(n, offset);
            }
        }

        private void UpdateRanks(TreeNode root, int offset)
        {
            foreach (TreeNode node in root.Nodes)
            {
                try
                {
                    Items.FromUrl(node.Name).Rank = 2 * node.Index + offset;
                }
                catch { }
            }
        }

        private void treeView_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(typeof(TreeNode)))
            {
                TreeNode node = e.Data.GetData(typeof(TreeNode)) as TreeNode;
                string test = node.Name;
                test = node.Text;
                e.Effect = DragDropEffects.Move;
            }
        }

        private void treeView_DragOver(object sender, DragEventArgs e)
        {
            // Check if it's on top of a different branch of the tree...
            TreeView tv = sender as TreeView;
            if (tv == null)
                return;
            Point pt = tv.PointToClient(new Point(e.X, e.Y));
            TreeNode node = tv.GetNodeAt(pt);
            if (node != null)
            {
                TreeNode FirstParent = node;
                while (FirstParent.Parent != null)
                {
                    FirstParent = FirstParent.Parent;
                }
                if (FirstParent.Text.IsSpecialStatus() && FirstParent == node)
                {
                    e.Effect = DragDropEffects.None;
                    tv.SelectedNode = null;
                }
                else
                {
                    e.Effect = e.AllowedEffect;
                    tv.SelectedNode = node;
                }
            }
        }

        private void treeView_GiveFeedback(object sender, GiveFeedbackEventArgs e)
        {
            
        }

        private void treeView_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
        {
            
        }

        private void btnRequestHolds_Click(object sender, EventArgs e)
        {
            TrySomeHolds(null);
        }
    }
}